<?php

/************************************************************
 * Copyright (C), 1993, Dacelve. Tech., Ltd.
 * FileName : Server.php
 * Author   : Lizhijian
 * Version  : 1.0
 * Date     : 2018/1/17 15:18
 * Description   :
 * Function List :
 * History  :
 * <author>    <time>    <version >    <desc>
 * Lizhijian   2018/1/17   1.0          init
 * Lizhijian   2018/2/12   1.1          optimize
 ***********************************************************/

/**
 * webSocket服务器通用应用类
 * Class Server
 */
class Server
{
    /**
     * 状态信息
     */
    const SEND_USER_LIST_NUM   = 50;//分批发送用户列表时的人数
    const OK_DATA_STATUS       = 200;
    const ERROR_DATA_STATUS    = 201;
    const ERROR_DATA_EMPTY     = 'ERROR_DATA_EMPTY: data is empty!';
    const ERROR_DATA_MSG_EMPTY = 'ERROR_DATA_MSG_EMPTY: data msg is empty!';
    const ERROR_DATA_FORMAT    = 'ERROR_DATA_FORMAT: data format is error! (Tip:must json data)';
    const ERROR_DATA_NUMBER    = 'ERROR_DATA_NUMBER: data number is error! (Tip:must true number)';
    const ERROR_PACK_EMPAY     = 'ERROR_PACK_EMPAY: data mapping pack is empty! (Tip:must true cmd)';
    const ERROR_PACK_CMD       = 'ERROR_PACK_CMD: data mapping pack cmd is error!';
    const ERROR_PACK_FORMAT    = 'ERROR_PACK_FORMAT: data mapping pack format is error!';
    const OK_DATA_MSG          = 'OK';//验证协议成功时使用
    const ERROR_DATA_MSG       = 'ERROR';//验证协议失败时使用
    const ERROR_SPEAK          = '你已被禁言！';//验证协议失败时使用
    const ERROR_SPEAK_STATUS   = 205;//验证协议失败时使用
    const VISITOR_HEAD         = [//游客头像
        'http://oss.phpyd.com/uploads/20180213/69e527d3ddd9f0f0030ed48ad1d3dc6d.jpg',
        'http://oss.phpyd.com/uploads/20180213/663482d0680bb12efacae968dc53357e.jpg',
        'http://oss.phpyd.com/uploads/20180216/156e603a701013e877c9f8d4ae71c01d.jpg',
        'http://oss.phpyd.com/uploads/20180216/2ae031f319a008fbea6544cf8e1e1292.jpg',
        'http://oss.phpyd.com/uploads/20180216/0ba43929bfc7c5b4e4b6fe7a3b19fd0f.jpg',
        'http://oss.phpyd.com/uploads/20180216/c200c8d91ab1f389b35e7ad2789ddca1.jpg',
        'http://oss.phpyd.com/uploads/20180216/c200c8d91ab1f389b35e7ad2789ddca1.jpg',
        'http://oss.phpyd.com/uploads/20180216/2014020b63daf00069c36596bd2f1b0e.jpg'
    ];
    const VISITOR_NAME         = '游客';//游客名
    const PACK_LIST            = [
        'getPackList' => [//获取现有协议包
            'cmd'
        ],
        'ping' => [//心跳包
            'cmd'
        ],
        'msg' => [//消息包(type=0广播、1组播、2互播)
            'cmd', 'msg', 'toMsg', 'srcClientId', 'toClientId', 'userId', 'room', 'group', 'data', 'type'
        ],
        'speak' => [//禁言
            'cmd', 'msg', 'srcClientId', 'toClientId', 'notAllowUserId', 'group', 'room', 'data', 'time'
        ],
        'gift' => [//送礼
            'cmd', 'msg', 'srcClientId', 'toClientId', 'userId', 'group', 'room', 'data', 'number'
        ],
        'kick' => [//踢出
            'cmd', 'msg', 'srcClientId', 'toClientId', 'userId', 'group', 'room', 'data'
        ],
        'login' => [//登录
            'cmd', 'type', 'userId', 'room', 'group'
        ],
        'userList' => [//用户列表
            'cmd','clientId'
        ],
        'ad' => [//全站公告
            'cmd','msg','data'
        ],
    ];

    /**
     * 服务器实例
     * @var
     */
    protected static $server;

    /**
     * 用户列表数据
     * @var array
     */
    protected static $userList;

    public static function __init($serv)
    {
        if(empty(self::$server)){
            self::$server = $serv;
        }
    }

    /**
     * 检查数据包是否正确
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $data
     * @param string $msg
     * @param int $code
     * @return array|string
     * @example
     */
    public static function check($data, $msg = self::OK_DATA_MSG, $code = self::OK_DATA_STATUS)
    {
        //是否空数据
        if (empty($data)) {
            return self::back(self::ERROR_DATA_EMPTY, '', 1, 201);
        }
        //是否JSON格式
        if (!self::is_json($data)) {
            return self::back(self::ERROR_DATA_FORMAT, '', 1, 201);
        }
        //是否主指令字段错误
        $data = json_decode($data, true);
        if (false === isset($data['cmd'])) {
            return self::back(self::ERROR_PACK_CMD, '', 1, 201);
        }
        //是否对应包不存在
        $tmp = self::PACK_LIST;
        if (empty($tmp[$data['cmd']])) {
            return self::back(self::ERROR_PACK_EMPAY, '', 1, 201);
        }
        //消息为空
        if ($data['cmd'] == 'msg' && $data['type'] ==0 && $data['msg'] == '') {
            return self::back(self::ERROR_DATA_MSG_EMPTY, '', 1, 201);
        }
        //是否字段数量错误
        $keys = array_keys($data);
        $pacKeys = array_keys(array_flip(self::PACK_LIST[$data['cmd']]));
        $diff = array_diff($pacKeys, $keys);
        if($diff){
            return self::back(self::ERROR_DATA_NUMBER, $diff, 1, 201);
        }
        //是否字段格式错误
        $sameCount = count(array_intersect($keys, $pacKeys));
        if ($sameCount != count($pacKeys)) {
            return self::back(self::ERROR_PACK_FORMAT, '', 1, 201);
        }

        return self::back('check ok', $data, 1, $code);
    }

    /**
     * 判断JSON格式
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $str
     * @return bool
     * @example
     */
    protected static function is_json($str)
    {
        if (is_array($str)) {
            return false;
        }
        $str = is_object(json_decode($str));
        switch ($str) {
            case 1;
                return true;
                break;
            default;
                return false;
                break;
        }
    }

    /**
     * 统一返回给客户端的方法
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param string $msg
     * @param array $data
     * @param int $type 0：json 1:array [数组形式用于验证时使用]
     * @param int $code
     * @return array|string
     * @example
     */
    public static function back($msg = self::OK_DATA_MSG, $data = [], $type = 0, $code = self::OK_DATA_STATUS)
    {
        $back = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];
        if (0 == $type) {
            $back = json_encode($back);
        }
        return $back;
    }

    /**
     * 获取现有协议包
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @return array
     * @example
     */
    public static function getPackList()
    {
        return self::PACK_LIST;
    }

    /**
     * 发送消息到所有客户端
     * @param $serv
     * @param $msg
     * @param $data
     * @author Lizhijian
     */
    public static function sendToAll($data = [], $msg = '')
    {
        foreach (self::$server->connections as $v) {
            self::$server->push($v, self::back($msg, $data));
        }
    }

    /**
     * 发送消息到房间内所有客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param string $msg
     * @param array $data
     * @param $room
     * @example
     */
    public static function sendLoginToRoom($data = [], $room, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $sendUserInfo = self::$server->userTable[$v];
            if (isset($sendUserInfo) && $sendUserInfo['room'] == $room ) {
                if($data['userId'] == $sendUserInfo['userId']){//是当前用户
                    $data['userName'] = $sendUserInfo['userName'];
                }
                self::$server->push($v, self::back($msg, $data));
            }
        }
    }

    public static function sendToRoom($data = [], $room, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $sendUserInfo = self::$server->userTable[$v];
            $userInfo = self::$server->userTable[$data['srcClientId']];
            if (isset($userInfo) && $sendUserInfo['room'] == $room) {
                $back = array_combine(self::PACK_LIST['msg'], [
                    $data['cmd'],
                    isset($data['msg'])? $data['msg']: ' ',
                    isset($data['toMsg'])? $data['toMsg']: ' ',
                    $data['srcClientId'],
                    $data['toClientId'],
                    $data['userId'],
                    $data['room'],
                    $data['group'],
                    $data['data'],
                    isset($data['type'])? $data['type']: 0
                ]);

                $back['userName'] = $userInfo['userName'];
                $back['headImg'] = $userInfo['headImg'];
                self::$server->push($v, self::back($msg, $back));
            }
        }
    }

    /**
     * 发送消息到房间某个组所有客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param string $msg
     * @param array $data
     * @param $room
     * @param $group
     * @example.
     */
    public static function sendToGroup($data = [], $room, $group, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $userInfo = self::$server->userTable[$v];
            $srcUserInfo = self::$server->userTable[$data['srcClientId']];
            if (isset($userInfo) && $userInfo['room'] == $room && $userInfo['group'] == $group) {
                $back = array_combine(self::PACK_LIST['msg'], [
                    $data['cmd'],
                    isset($data['msg'])? $data['msg']: ' ',
                    isset($data['toMsg'])? $data['toMsg']: ' ',
                    $data['srcClientId'],
                    $data['toClientId'],
                    $data['userId'],
                    $data['room'],
                    $data['group'],
                    $data['data'],
                    isset($data['type'])? $data['type']: 0
                ]);

                $back['userName'] = $srcUserInfo['userName'];
                $back['headImg'] = $srcUserInfo['headImg'];

                self::$server->push($v, self::back($msg, $back));
            }
        }
    }

    /**
     * 发送消息给某个客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param $fd
     * @param string $msg
     * @param array $data
     * @example
     */
    public static function sendToOne($fd, $data = [], $msg = self::OK_DATA_MSG, $code = self::OK_DATA_STATUS){
        $userInfo = self::$server->userTable[$fd];
        if(empty($data['userName'])){
            $data['userName'] = $userInfo['userName'];
        }
        if(empty($data['headImg'])){
            $data['headImg'] = $userInfo['headImg'];
        }
        self::$server->push($fd, self::back($msg, $data, 0, $code));
    }

    /**
     * 记录日志
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $data
     * @example
     */
    public static function record($data)
    {
        var_dump($data);
    }

    public static function showTable($table)
    {
        foreach ($table as $v) {
            var_dump($v);
        }
    }

    /**
     * 服务器调用本方法会变成守护进程
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @example
     */
    public static function daemonize()
    {
        $pid = pcntl_fork();
        if ($pid == -1) {
            die("fork(1) failed!\n");
        } elseif ($pid > 0) {
        //让由用户启动的进程退出
            exit(0);
        }

        //建立一个有别于终端的新session以脱离终端
        posix_setsid();

        $pid = pcntl_fork();
        if ($pid == -1) {
            die("fork(2) failed!\n");
        } elseif ($pid > 0) {
        //父进程退出, 剩下子进程成为最终的独立进程
            exit(0);
        }
    }

    /**
     * 获取连接数量
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $server
     * @example
     */
    public static function getConnectNum(){
        echo "当前服务器共有 ".count(self::$server->connections). " 个连接\n";
    }



    public static function sendUserListToLoginer($fd, $msg = ''){
        $send = $data = [];
        $send['code'] = 200;
        $send['cmd'] = 'userList';

        $LoginInfo = self::$server->userTable[$fd];
        foreach (self::$server->connections as $v) {
            $userInfo = self::$server->userTable[$v];
            if($userInfo['room'] != $LoginInfo['room']){
                continue;
            }
            $data[] = $userInfo;
        }
        //用户分批发送
        $size = self::SEND_USER_LIST_NUM;
        $tmp = array_chunk($data, $size);
        foreach ($tmp as $v_data){
            self::$server->after(10,function ()use($fd, $msg, $v_data, $send){
                $send['data'] = $v_data;
                self::$server->push($fd, self::back($msg, $send));
            });
        }
    }

    public static function sendUserList($loginFd, $msg = ''){
        $send = [];
        $send['code'] = 200;
        $send['cmd'] = 'userList';

        $loginInfo = self::$server->userTable[$loginFd];
        $send['data'][] = $loginInfo;
        foreach (self::$server->connections as $v) {
            $toUserInfo = self::$server->userTable[$v];
            if($v == $loginFd || $loginInfo['room'] != $toUserInfo['room']){
                continue;
            }
            self::$server->push($v, self::back($msg, $send));
        }
    }
}

